﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Runtime.Serialization;

namespace Genuilder.Extensibility
{

	[DataContract]
	public class FileQuery
	{
		[DataContract]
		public class FileInDirectoryQuery
		{
			internal FileQuery fileQuery;


			public FileInDirectoryQuery(FileQuery fileQuery, string path, bool recursive)
			{
				this.fileQuery = fileQuery;
				this.Path = path;
				this.Recursive = recursive;
				EndWithQueries = new List<string>();
				LikeQueries = new List<string>();
			}


			bool IsLocal
			{
				get
				{
					return Path == ".";
				}
			}

			[DataMember]
			public List<String> EndWithQueries
			{
				get;
				set;
			}
			[DataMember]
			public List<String> LikeQueries
			{
				get;
				set;
			}

			[DataMember]
			public bool SelectAll
			{
				get;
				set;
			}

			[DataMember]
			public bool Recursive
			{
				get;
				set;
			}

			[DataMember]
			public String Path
			{
				get;
				set;
			}

			[DataMember]
			public String SourceType
			{
				get;
				set;
			}

			public FileInDirectoryQuery All()
			{
				SelectAll = true;
				return this;
			}

			public FileInDirectoryQuery Like(string fileRegex)
			{
				LikeQueries.Add(fileRegex);
				return this;
			}

			public FileInDirectoryQuery EndWith(string str)
			{
				EndWithQueries.Add(str);
				return this;
			}

			public FileInDirectoryQuery OfSourceType(SourceType sourceType)
			{
				SourceType = sourceType.ToString();
				return this;
			}
			

			public FileQuery Back
			{
				get
				{
					return fileQuery;
				}
			}

			public FileQuery ToQuery()
			{
				return fileQuery;
			}

			#region IFileSelector Members

			internal bool IsSelected(SourceFile sourceFile)
			{
				if(SourceType != null && sourceFile.SourceType.ToString() != SourceType)
					return false;

				var file = sourceFile.Name;
				var fileParts = file.Split(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
				var pathParts = Path.Split(System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
				if(IsLocal)
				{
					if(fileParts.Length == 1)
						return Evaluate(fileParts);
					else if(Recursive && IsSubDirectory(file))
						return Evaluate(fileParts);
					else
						return false;
				}

				if(fileParts.Length - 1 < pathParts.Length)
					return false;
				for(int i = 0; i < fileParts.Length - 1; i++)
				{
					var filePart = fileParts[i];
					if(i >= pathParts.Length)
					{
						return Recursive && Evaluate(fileParts);
					}
					var pathPart = pathParts[i];
					if(filePart != pathPart)
						return false;
				}
				return true && Evaluate(fileParts);
			}

			private bool IsSubDirectory(string file)
			{
				return new FileInfo(file).FullName.Contains(new DirectoryInfo(Path).FullName);
			}

			private bool Evaluate(string[] fileParts)
			{
				if(SelectAll)
					return true;
				var endWith = EndWithQueries.Any(e => fileParts.Last().EndsWith(e));
				if(endWith)
					return endWith;
				var like = LikeQueries.Any(s => Regex.Match(fileParts.Last(), s).Success);
				return like;
			}

			#endregion
		}


		public FileQuery()
		{
			Selectors = new List<FileInDirectoryQuery>();
			Names = new List<string>();
		}

		[OnDeserialized]
		private void Deserialized(StreamingContext context)
		{
			foreach(var selector in Selectors)
				selector.fileQuery = this;
		}

		[DataMember]
		public List<FileInDirectoryQuery> Selectors
		{
			get;
			set;
		}

		[DataMember]
		public List<String> Names
		{
			get;
			set;
		}

		private FileInDirectoryQuery Add(FileInDirectoryQuery selector)
		{
			Selectors.Add(selector);
			return selector;
		}


		public FileQuery SelectByName(string name)
		{
			if(name == null)
				return this;
			Names.Add(name);
			return this;
		}

		public FileQuery SelectByNames(IEnumerable<string> names)
		{
			if(names == null)
				return this;
			foreach(var name in names)
				SelectByName(name);
			return this;
		}

		public FileQuery SelectByNames(params string[] names)
		{
			return SelectByNames((IEnumerable<string>)names);
		}


		private bool SelectedByName(SourceFile file)
		{
			if(Names.Contains(file.Name))
				return true;
			else
			{
				if(Names.Contains(file.Name.Split('\\', '/').Last()))
					return true;
			}
			return false;
		}

		public FileInDirectoryQuery SelectInDirectory(string path, bool recursive = false)
		{
			return Add(new FileInDirectoryQuery(this, path, recursive));
		}

		public FileInDirectoryQuery SelectInThisDirectory(bool recursive = false)
		{
			return Add(new FileInDirectoryQuery(this, ".", recursive));
		}

		public FileQuery Clone()
		{
			DataContractSerializer serializer = new DataContractSerializer(typeof(FileQuery));
			MemoryStream ms = new MemoryStream();
			serializer.WriteObject(ms, this);
			ms.Position = 0;
			return (FileQuery)serializer.ReadObject(ms);
		}


		public IEnumerable<string> Fetch(IEnumerable<string> files)
		{
			return Fetch(files.Select(f => new SourceFile(f, SourceType.Compile))).Select(f => f.Name);
		}

		public IEnumerable<SourceFile> Fetch(IEnumerable<SourceFile> files)
		{
			foreach(var file in files)
			{
				if(CanFetch(file))
					yield return file;
			}
		}

		public bool CanFetch(SourceFile file)
		{
			var selected = Selectors.Any(s => s.IsSelected(file));
			if(selected)
				return true;
			else
			{
				if(SelectedByName(file))
					return true;
			}
			return false;
		}
	}
}
